package template.utils.cqs;

import com.google.common.eventbus.Subscribe;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 59960549
 */
@Slf4j
@Component
public class Bus implements ApplicationListener<ApplicationStartedEvent> {

    @Autowired
    private ApplicationContext context;

    private final Map<Class<?>, Object> targetMap = new HashMap<>();
    private final Map<Class<?>, Method> methodMap = new HashMap<>();

    /**
     * 向 commandHandlerMap 中添加一条键值对。
     */
    private void registerSubscribeMethod(Object service, Method method) {

        for (Parameter parameter : method.getParameters()) {
            log.info("handler can deal with: " + parameter.getType());
            Class<? extends Object> commandType = parameter.getType();
            targetMap.put(commandType, service);
            methodMap.put(commandType, method);
        }
    }

    /**
     * 编程技巧和语法规则的一个双重要求：方法中如果涉及到泛型参数，不要出现 ? extends xxx 的形式。
     * 可以将你想表达的这个含义，用泛型参数定义出来。
     */
    public <C extends Object> Object post(C command) {
        Class<C> commandType = (Class<C>) command.getClass();
        Method method = methodMap.get(commandType);
        Object target = targetMap.get(commandType);

        return ReflectionUtils.invokeMethod(method, target, command);
    }

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        log.info("容器已启动，进行 Bus 的初始化");

        String[] serviceNames = context.getBeanNamesForAnnotation(Service.class);
        for (String name : serviceNames) {
            Object service = context.getBean(name);
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(service.getClass());
            for (Method method : methods) {
                if (method.getAnnotation(Subscribe.class) == null) {
                    continue;
                }
                registerSubscribeMethod(service, method);
            }
        }

    }
}
